const JSZip = require('jszip');
const DocxTemplate = require('docxtemplater');
const expressions = require('angular-expressions');
const path = require('path');
const fs = require('fs-extra');
const compressing = require('compressing');
const randomstring = require('randomstring');
const utility = require('./../../../utility');
const cacheUtil = require('./../../../../cacheUtil');
const { parseXml, getRelations, getImageReplaceMap } = require('./helper');
const logger = require('../../../logger');

// 注册自定义where函数
expressions.filters.where = function where(input, condition) {
  if (!utility.isArrayType(input)) {
    return input;
  }
  // 编译成可执行函数
  const condFn = expressions.compile(condition);
  // 逐个判断
  return input.filter((item) => {
    try {
      const result = !!condFn(item);
      return result;
    } catch (e) {
      logger.error(`Error evaluating condition:${condition} on item:${item}`, e);
      return false;
    }
  });
};

const angularParser = (tag) => {
  const expr = expressions.compile(tag);
  return { get: expr };
};
class Docx {
  constructor(template) {
    this.docxTemplate = new DocxTemplate();
    if (template) {
      this.template = template;
    }
    this.docxTemplate.setOptions({ parser: angularParser });
    this.loadTemplate = false;
  }

  getAdapterInstance() {
    return this.docxTemplate;
  }

  generate(data, outputPath, toc = false) {
    let loadTemplate = Promise.resolve();
    if (this.template && !this.loadTemplate) {
      loadTemplate = utility.readFile(this.template, 'binary')
        .then((templateData) => {
          this.docxTemplate.loadZip(new JSZip(templateData));
          const map = new Map();
          Object.keys(this.docxTemplate.zip.files).forEach((fileName) => {
            const fileReg = /^word\/_rels\/.+/g;
            if (fileReg.test(fileName)) {
              const prefix = fileName.match(/^word\/_rels\/(.+)\.xml\.rels/i)[1];
              const document = parseXml(Buffer.from(this.docxTemplate.zip.files[`word/${prefix}.xml`]._data.getContent()));
              const documentRels = parseXml(Buffer.from(this.docxTemplate.zip.files[fileName]._data.getContent()));
              /** @type Map */
              const relMap = getRelations(documentRels);
              /** @type Map */
              const replacementMap = getImageReplaceMap(document);
              replacementMap.forEach((val, key) => {
                const filePth = relMap.get(key);
                if (filePth) {
                  map.set(filePth, val);
                }
              });
            }
          });
          this.loadTemplate = true;
          // console.log(map);
          return Promise.resolve(map);
        });
    }
    const basePath = path.join(cacheUtil.getConfigPath('exportDir'), randomstring.generate(16));
    return loadTemplate
      .then((replacementMap) => {
        data.__basePath__ = basePath;
        fs.emptyDirSync(basePath);
        this.docxTemplate.setData(data);
        this.docxTemplate.render();
        const { zip, targets } = this.docxTemplate;
        Object.keys(zip.files).forEach((fp) => {
          if (fp !== 'word/settings.xml' && (zip.files[fp].dir || targets.indexOf(fp) >= 0)) {
            return;
          }
          fs.ensureDirSync(path.join(basePath, path.dirname(fp)));
          if (zip.files[fp]._data.getContent) {
            fs.writeFileSync(path.join(basePath, fp), zip.files[fp]._data.getContent());
          } else {
            if (toc && fp === 'word/settings.xml' && zip.files[fp]._data.indexOf('<w:updateFields w:val="true"/>') < 0) {
              zip.files[fp]._data = zip.files[fp]._data.replace('</w:settings>', '<w:updateFields w:val="true"/></w:settings>');
            }
            fs.writeFileSync(path.join(basePath, fp), zip.files[fp]._data);
          }
        });
        // image replacement
        replacementMap.forEach((dataKey, filePth) => {
          fs.copyFileSync(data[dataKey], path.join(basePath, 'word', filePth));
        });

        const zipStream = new compressing.zip.Stream();
        fs.readdirSync(basePath).forEach((item) => {
          zipStream.addEntry(path.join(basePath, item));
        });
        return new Promise((res, rej) => {
          zipStream
            .pipe(fs.createWriteStream(outputPath))
            .once('close', () => {
              fs.removeSync(basePath);
              res();
            })
            .once('error', rej);
        });
      })
      .then(() => outputPath);
  }

  setTemplate(templatePath) {
    this.template = templatePath;
    return this;
  }
}

module.exports = Docx;
